package NBU::Drive;

require 5.002;

use strict;
use Carp;

BEGIN {
  use Exporter   ();
  use AutoLoader qw(AUTOLOAD);
  use vars       qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $AUTOLOAD);
  $VERSION =     '0.01';
  @ISA =         qw();
  @EXPORT =      qw();
  @EXPORT_OK =   qw();
  %EXPORT_TAGS = qw();
}

my %drivePool;

sub new {
  my $Class = shift;
  my $drive = {};

  bless $drive, $Class;

  if (@_) {
    $drive->{ID} = shift;
    $drivePool{$drive->{ID}} = $drive;

    $drive->{STATUS} = "DOWN";;
    $drive->{HANDLERS} = {};
  }
  return $drive;
}

sub byID {
  my $Class = shift;
  my $driveID = shift;
  my $drive;

  if (!($drive = $drivePool{$driveID})) {
    $drive = NBU::Drive->new($driveID);
  }
  return $drive;
}

sub populate {
  my $Class = shift;
  my $server = shift;

  open(PIPE, $NBU::prefix."volmgr/bin/vmoprcmd -h ".$server->name." -d ad |");
  my $inDriveList;
  while (<PIPE>) {
    if (!$inDriveList && /^Drv/) {
      $inDriveList = 1;
    }
    elsif ($inDriveList) {
      my ($ignore, $id, $name, $multiHost, $assigned, $comment) = split(/\s+/, $_, 6);
      chop $comment;

      my $drive = NBU::Drive->byID($id);
      $drive->{HOST} = NBU::Host->byName($server);
      $drive->{NAME} = $name;
      $drive->{MULTIHOST} = ($multiHost ne "No");
      $drive->{COMMENT} = $comment;
    }
  }
  close(PIPE);
}

sub updateStatus {
  my $Class = shift;
  my $server = shift;

  open(PIPE, $NBU::prefix."volmgr/bin/vmoprcmd -h $server -d ds |");
  my $inDriveList;
  while (<PIPE>) {
    if (!$inDriveList && /^Drv/) {
      $inDriveList = 1;
    }
    elsif ($inDriveList) {
      my ($ignore, $id, $type, $control, $user, $label, @remainder) = split(/\s+/, $_);
      my ($rvsn, $evsn);
      if ($label eq "Yes") {
	$rvsn = shift @remainder;
	$evsn = shift @remainder;
      }
      my ($ready, $writeEnabled, $requestID) = @remainder;
      my $drive = NBU::Drive->byID($id);
      $drive->status($control);
      if ($drive->{INUSE} && ($ready eq "No")) {
	$drive->free(time);
      }
      elsif (!$drive->{INUSE} && ($ready eq "Yes")) {
	$drive->use(undef, time);
      }
    }
  }
  close(PIPE);
}

sub pool {
  my $Class = shift;

  return (values %drivePool);
}

sub status {
  my $self = shift;

  if (@_) {
    my $oldStatus = $self->{STATUS};
    my $control = shift;
    my $newStatus = ($control =~ /DOWN/) ? "DOWN" : "UP";

    if ($oldStatus ne $newStatus) {
      my $handlers = $self->{HANDLERS};

      if (exists($$handlers{$newStatus})) {
	my $handler = $$handlers{$newStatus};
	&$handler($self, $newStatus);
      }
    }

    $self->{STATUS} = $newStatus;
  }
  return $self->{STATUS};
}

#
# If the drive is not up, try to change its state to up.  Then,
# if an argument is provided the host will be queried to see if the
# drive indeed came back up.  Without argument the code assumes all
# went as planned.
sub up {
  my $self = shift;


  if ($self->{STATUS} ne "UP") {
    system($NBU::prefix."volmgr/bin/vmoprcmd -up ".$self->id." -h ".$self->host."\n");
    if (@_) {
      $self->updateStatus($self->host);
    }
    else {
      $self->status("UP");
    }
  }

  return $self->{STATUS} eq "UP";
}

#
# Same story as the up routine just above
sub down {
  my $self = shift;

  if ($self->{STATUS} ne "DOWN") {
    system($NBU::prefix."volmgr/bin/vmoprcmd -down ".$self->id." -h ".$self->host."\n");
    if (@_) {
      $self->updateStatus($self->host);
    }
    else {
      $self->status("DOWN");
    }
  }

  return $self->{STATUS} eq "DOWN";
}

sub busy {
  my $self = shift;

  return $self->{INUSE};
}

sub id {
  my $self = shift;

  if (@_) {
    $self->{ID} = shift;
    $drivePool{$self->{ID}} = $self;
  }

  return $self->{ID};
}

sub comment {
  my $self = shift;

  if (@_) {
    $self->{COMMENT} = shift;
  }

  return $self->{COMMENT};
}

sub name {
  my $self = shift;

  return $self->{NAME};
}

sub host {
  my $self = shift;

  return $self->{HOST};
}

sub use {
  my $self = shift;
  my ($mount, $tm) = @_;

  if ($self->{INUSE}) {
    $self->free($tm);
  }

  my $uses = $self->usage;

  my %use;
  $use{'MOUNT'} = $mount;
  $self->{INUSE} = $use{'START'} = $tm;
  push @$uses, \%use;
  return $self;
}

sub free {
  my $self = shift;
  my $tm = shift;

  if (!$self->{INUSE}) {
# it is quite common for a mount to inform the drive it is no
# longer using the drive sometime after the drive has been put
# to new use already.  Hence ignore this event.
#    print "Drive ".$self->id." already free!\n";
#    exit(0);
  }
  $self->{INUSE} = undef;

  my $uses = $self->usage;
  my $use = pop @$uses;
  $$use{'STOP'} = $tm;
  push @$uses, $use;

  return $self;
}

sub lastUsed {
  my $self = shift;

  my $uses = $self->usage;
  if (my $use = pop @$uses) {
    return $$use{'START'};
  }
  else {
    return 0;
  }
}

sub usage {
  my $self = shift;

  if (!$self->{USES}) {
    $self->{USES} = [];
  }

  return $self->{USES};
}

sub busyStats {
  my $self = shift;
  my $asOf = shift;
  my $endOfPeriod = shift;

  my $stepSize = 5 * 60;
  $stepSize = shift if (@_);

  my $usage = $self->usage;

  my $step = $asOf;
  my $use = shift @$usage;
  my $mount = $$use{MOUNT};
  my $job = $mount->job;
  my $du = 1;

  my @driveInUse;
  while ($step < $endOfPeriod) {
    if (!defined($use) || ($step < $$use{START})) {
      push @driveInUse, 0;
    }
    elsif ($step < $$use{STOP}) {
      push @driveInUse, $du;
    }
    else {
      $use = shift @$usage;
      if (defined($use) && defined($mount = $$use{MOUNT})) {
	$du = 1;
      }
      else {
	$du = 0;
      }
      next;
    }
    $step += $stepSize;
  }

  return ($asOf, $endOfPeriod, $stepSize, @driveInUse);
}

sub notifyOn {
  my $self = shift;
  my $target = shift;
  my $handler = shift;

  my $handlers = $self->{HANDLERS};
  $$handlers{$target} = $handler;
}

1;

__END__
